/*
 *  Copyright (c) 2010 Mathew Hall.
 *  All rights reserved.
 * 
 *  Redistribution and use in source and binary forms, with or
 *  without modification, are permitted provided that the following conditions
 *  are met:
 * 
 *  Redistributions of source code must retain the above copyright
 *  notice, this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above
 *  copyright notice, this list of conditions and the following
 *  disclaimer in the documentation and/or other materials provided
 *  with the distribution.
 *  Neither the name of the University of Sheffield nor the names of its
 *  contributors may be used to endorse or promote products derived
 *  from this software without specific prior written permission.
 * 
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 *  CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 *  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 *  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 *  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package main;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jgap.InvalidConfigurationException;
import primitives.cluster.ClusterHead;
import primitives.graph.Graph;
import search.fitnessfunctions.TreeFitnessFunction;
import search.searchtechniques.GeneticAlgorithmSearch;
import search.searchtechniques.RandomMutationHillClimbingSearch;
import search.searchtechniques.Search;
import search.util.*;

/**
 * Runs a list of Experiments over as many threads as there are cores.
 * Usage: instantiate, then use instance.runExperiments()
 * @author Mathew Hall
 */
public class ExperimentRunner implements Runnable {

    private ThreadCount semaphore;
    private Experiment td;


    private ProgressStatus monitor;


    public void setRunnerMonitor(ProgressStatus mon){
        monitor = mon;
    }

    public ArrayList<Experiment> runExperiments(Queue<Experiment> expts) {

        ArrayList<Experiment> results = new ArrayList<Experiment>();
        ThreadCount sem = null;
        boolean set = false;

        StatusMonitor sm = new StatusMonitor();

        new Thread(sm, "StatusMonitor").start();
        int startSize = expts.size();
        int iter = 0;
        while (!expts.isEmpty()) {
            Experiment current = expts.remove();
            if(!set){
                set = true;
                sem = new ThreadCount(current.getNumThreads());
            }
            sem.inc();
            ExperimentRunner inst = new ExperimentRunner(current, sem);
            ProgressStatus ps = new ProgressStatus(sm);

            ps.id = "["+current.getExperimentName() +   " #" + this.hashCode() + "] " + current.getInputname().getName();

            double prog = 1.0 - (expts.size() / (1.0* startSize));
            iter++;
            ps.id += " (" + iter + "/" + startSize  + "%)";

            Logger.getLogger(ExperimentRunner.class.getName()).log(Level.FINE, current.getExperimentName() + ": Progress: " + (prog * 100) + "%");


            inst.setRunnerMonitor(ps);
            Thread t = new Thread(inst, "ExperimentRunner Slave Thread " + iter);
            t.start();
            results.add(current);
            
        }

        sem.waitForZero();
        sm.pushEvent(new ProgressEvent(ProgressEvent.TYPE_SHUTDOWN, -1, "",""));

        return results;

    }

    private ExperimentRunner(Experiment toDo, ThreadCount semaphore) {
        this.semaphore = semaphore;
        this.td = toDo;
    }

    public ExperimentRunner() {
    }

    public void run() {
        try {
            Class<?> ffClass = Object.class;

            try {
                ffClass = Class.forName(td.getFitnessFunction(), true, this.getClass().getClassLoader());
            
            } catch (ClassNotFoundException ex) {
                Logger.getLogger(ExperimentRunner.class.getName()).log(Level.SEVERE, "Failed to load fitness function " + td.getFitnessFunction(), ex);
            } //TODO: add support for Bunch adapted FFs (these require constructors)
            TreeFitnessFunction fitnessFunction = null;
            try {
                fitnessFunction = (TreeFitnessFunction) ffClass.newInstance();
            } catch (InstantiationException ex) {
                Logger.getLogger(ExperimentRunner.class.getName()).log(Level.SEVERE, "Failed to instantiate fitness function " + td.getFitnessFunction(), ex);
            } catch (IllegalAccessException ex) {
                Logger.getLogger(ExperimentRunner.class.getName()).log(Level.SEVERE, "IllegalAccessException when instantiating " + td.getFitnessFunction(), ex);
            }

            ClusterHead toCluster = TreeLoader2.loadTreeFromDot(td.getInputname());



            Logger.getLogger(ExperimentRunner.class.getName()).log(Level.FINE, String.format("Starting clustering of file: %s [%d nodes, %d edges]"
                    , td.getInputname().getName()
                    ,toCluster.getSize()
                    ,new Graph(toCluster.getNodes()).countTransitions()
                    ));
            
            Search gs = null;          
            if(td.getSearchType().equals("GA")){
                gs = new GeneticAlgorithmSearch();
            }else if(td.getSearchType().equals("RMHC")){
                gs = new RandomMutationHillClimbingSearch();
            }else{
                throw new RuntimeException(String.format("Chosen search type %s is not a valid option", td.getSearchType()));
            }
            

            gs.setPopulationSize(td.getPopsize());
            gs.setChromosomeSize(td.getChromesize());
            gs.setGenerations(td.getNumgens());
            gs.setFitnessFunction(fitnessFunction);
            gs.setInput(toCluster);
            gs.setGeneType(td.getGeneType());
			
			gs.setExperimentName(td.getExperimentName());

			gs.setFitnessBudget(td.getFitnessBudget());

            
            SearchResult sr = null;
            gs.setProgressStatus(monitor);

            gs.setName(td.getInputname().toString());

            sr = gs.start();

			

            td.setResult(sr);
            try {
                td.storeAll();
            } catch (IOException ex) {
                Logger.getLogger(ExperimentRunner.class.getName()).log(Level.SEVERE, null, ex);
            } catch (ExperimentNotRunException ex) {
                Logger.getLogger(ExperimentRunner.class.getName()).log(Level.SEVERE, null, ex);
            }

            semaphore.dec();
        } catch (InvalidConfigurationException ex) {
            Logger.getLogger(ExperimentRunner.class.getName()).log(Level.SEVERE, String.format("Invalid JGAP configuration %s","for file " + td.getInputname().toString()), ex);
            semaphore.dec();
        } catch (IOException ex) {
            Logger.getLogger(ExperimentRunner.class.getName()).log(Level.SEVERE, "IOException when saving results", ex);
            semaphore.dec();
        }catch(IllegalStateException ex){
            Logger.getLogger(ExperimentRunner.class.getName()).log(Level.SEVERE, String.format("Illegal State Exception: %s",td.getInputname()), ex);
            semaphore.dec();
        }catch(Exception e){
				Logger.getLogger(ExperimentRunner.class.getName()).log(Level.SEVERE, String.format("Exception raised in GA, this result will be invalid: %s: %s",e.getMessage(),e.toString()), e) ;
				semaphore.dec();
		}

    }
}
